package com.cheng.im.client;

import com.cheng.im.client.clientHandler.ChatMsgHandler;
import com.cheng.im.client.clientHandler.ExceptionHandler;
import com.cheng.im.client.clientHandler.LoginResponceHandler;
import com.cheng.im.client.protoBuilder.LoginMsgBuilder;
import com.cheng.im.client.session.ClientSession;
import com.cheng.im.common.bean.UserDTO;
import com.cheng.im.common.bean.msg.ProtoMsgV1;
import com.cheng.im.common.codec.ProtobufDecoder;
import com.cheng.im.common.codec.ProtobufEncoder;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.util.concurrent.locks.LockSupport;

/**
 * @author Cheng
 */
@Slf4j
@Data
@Service
public class SoulTest1Client {

    //请求的时间间隔，单位为ms
    public static long serverPortCount = 10;

    //连接上限
    public static long maxConnection = 100;

    // 开始的用户id
    public static long startUserID = 1;

    private Bootstrap bootstrap;

    private EventLoopGroup group = new NioEventLoopGroup();

    @Value("${connect.ip}")
    private String ip;
    @Value("${connect.port}")
    private int port;

    @Autowired
    private ChatMsgHandler chatMsgHandler;

    @Autowired
    private LoginResponceHandler loginResponceHandler;


    @Autowired
    private ExceptionHandler exceptionHandler;

    /**
     * 执行连接
     */
    public void doConnect() {
        try {
            bootstrap = new Bootstrap();

            bootstrap.group(group);
            bootstrap.channel(NioSocketChannel.class);
            bootstrap.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);


            /**
             * SO_REUSEADDR提供如下四个功能：
             *
             *     SO_REUSEADDR允许启动一个监听服务器并捆绑其众所周知端口，即使以前建立的将此端口用做他们的本地端口的连接仍存在。这通常是重启监听服务器时出现，若不设置此选项，则bind时将出错。
             *
             *     SO_REUSEADDR允许在同一端口上启动同一服务器的多个实例，只要每个实例捆绑一个不同的本地IP地址即可。对于TCP，我们根本不可能启动捆绑相同IP地址和相同端口号的多个服务器。
             *
             *     SO_REUSEADDR允许单个进程捆绑同一端口到多个套接口上，只要每个捆绑指定不同的本地IP地址即可。这一般不用于TCP服务器。
             *
             *     SO_REUSEADDR允许完全重复的捆绑：当一个IP地址和端口绑定到某个套接口上时，还允许此IP地址和端口捆绑到另一个套接口上。一般来说，这个特性仅在支持多播的系统上才有，而且只对UDP套接口而言（TCP不支持多播）。
             */
            bootstrap.option(ChannelOption.SO_REUSEADDR, true);

            // 设置通道初始化
            bootstrap.handler(
                    new ChannelInitializer<SocketChannel>() {
                        public void initChannel(SocketChannel ch) {
                            ch.pipeline().addLast("decoder", new ProtobufDecoder());
                            ch.pipeline().addLast("encoder", new ProtobufEncoder());
                            ch.pipeline().addLast("loginResponseHandler", loginResponceHandler);
                            ch.pipeline().addLast("chatMsgHandler", chatMsgHandler);
                            ch.pipeline().addLast("exceptionHandler", exceptionHandler);
                        }
                    }
            );
            log.info("客户端开始连接 [soul test 1 Server]");

            //客户端无限制发起连接，直到上限
            int indexOfServerPort = 0;
            int connectionCount = 0;

            while (!Thread.interrupted()) {
                try {
                    ChannelFuture channelFuture = bootstrap.connect(ip, port);
                    channelFuture.addListener((ChannelFutureListener) future -> {
                        if (!future.isSuccess()) {
                            log.debug("connect failed, exit!");
                            System.exit(0);
                        } else {
                            log.debug("connect success!!");
                            // 执行登录逻辑
                            // 只需要构建一个user就行了
                            UserDTO user = new UserDTO();
                            user.setUserId(String.valueOf(startUserID));
                            user.setToken("soulTest");
                            user.setDevId("100");

                            startUserID++;

                            // 发送登录信息
                            log.info("发送登录消息");


                            Channel channel = future.channel();

                            ClientSession clientSession = new ClientSession(channel);
                            clientSession.setUser(user);

                            ProtoMsgV1.Message message =
                                    LoginMsgBuilder.buildLoginMsg(user, clientSession);

                            channel.writeAndFlush(message);
                            log.info("发送登录消息完成");
                        }
                    });
                    channelFuture.get();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                ++indexOfServerPort;
                if (indexOfServerPort == serverPortCount) indexOfServerPort = 0;
                if (++connectionCount == maxConnection) break;
            }

            // 睡眠
            LockSupport.parkNanos(Integer.MAX_VALUE * 1000L * 1000L);

        } catch (Exception e) {
            log.info("客户端连接失败!" + e.getMessage());
        }
    }
}
